/*
 * ExtensibleHashIndexStratregy.cpp
 *
 *  Created on: 03/10/2009
 *      Author: nik0l4z
 */

#include "../Strategies/Index/ExtensibleHashIndexStratregy.h"



ExtensibleHashIndexStratregy::ExtensibleHashIndexStratregy() {

}

ExtensibleHashIndexStratregy::ExtensibleHashIndexStratregy(int tamaniocub, const string& nombrearch, StorageStrategy *storage) {

	const string sufijotabla="INDICEHASH";

	Blockstorage=static_cast <BlockStorageStrategy*> (storage);

	nombrearchivo=nombrearch + sufijotabla;

	this->TablaDisp= new TablaTamanioDisp(nombrearch);

	this->tabla= new Tabladirecciones(nombrearch);

	this->tamanioCubo=tamaniocub;

	arch= new ArchivoTexto(nombrearchivo);

	Deserealizar();

}

ExtensibleHashIndexStratregy::~ExtensibleHashIndexStratregy() {
	Serializar();
	delete tabla;
	delete TablaDisp;
	delete arch;

}




void ExtensibleHashIndexStratregy::Deserealizar()
{


		string cadena;
		string cadenaaux;
		char c='0';
		int i;
		int Archcreado=0;

		arch->leerLinea(cadena);

		while ((cadena.capacity()!=0)&& (cadena.empty()==0))

		{
		i =0;
		c='0';
		Archcreado=1;

		while (c!='.')
		{

			c=cadena.at(i);

			if ((c!='.')&&(c!='/')&&(c!='%'))
			{
				cadenaaux.push_back(c);
			}

			if (c=='/')
			{
				ultimonrobloque=  atoi(cadenaaux.c_str());

				cadenaaux.clear();


			}

			if (c=='%')
			{

				NroCubosDisponibles.push_back(atoi(cadenaaux.c_str()));

				cadenaaux.clear();
			}

			i++;


		};

		arch->leerLinea(cadena);

		};

		if ((cadena.capacity()==0) || ( Archcreado=0 ))
			{


				ultimonrobloque=0;

				if (NroCubosDisponibles.empty())
				{
					NroCubosDisponibles.reserve(1);
				}

				Bloque cubo(ultimonrobloque,tamanioCubo);

				cubo.setEspacioLibre(this->tamanioCubo);

				this->escribircubo(cubo,ultimonrobloque);

				delete cubo.getRegistros();

				this->TablaDisp->SetTamanioDisp(ultimonrobloque,1);

				arch->irAlPrincipio();

				Serializar();

				return;
			}

		return;

}


void ExtensibleHashIndexStratregy::Serializar()
{


	string cadena;
	const char separador1= '/';
	const char separador2= '%';
	unsigned int i=0;
	int longitud;
	int FIN=0;

	arch->clean();
	arch->irAlPrincipio();

	std::string s;
   	std::stringstream out;

   	out << ultimonrobloque;

   	s=out.str();

   	cadena=cadena+s;

   	s.clear();

   	cadena.push_back(separador1);

	if (NroCubosDisponibles.empty())
	{
		cadena.push_back('.');


		arch->escribir(cadena);


		cadena.clear();

		arch->terminarLinea();

		return;
	}

	while (FIN==0)
	{

		longitud=0;

	for(;((i<NroCubosDisponibles.size())&&( longitud < 10 ));i++)
	{
	   	std::string s;
	   	std::stringstream out;

	   	out << NroCubosDisponibles[i];

	   	s=out.str();

	   	cadena=cadena+s;

	   	s.clear();

		cadena.push_back(separador2);

		longitud++;

	}

	if ((i==NroCubosDisponibles.size()) && (longitud ==0))
	{

		FIN=1;

		return;
	}

	cadena.push_back('.');

	arch->escribir(cadena);

	cadena.clear();

	arch->terminarLinea();

	}


}


int ExtensibleHashIndexStratregy::funcionhash(Clave clave){

	string clavestring;
	unsigned long int hash=0;
	int resultado;
	size_t pos;
	size_t tamaniostring;
	int tamaniotabla;

	clavestring=clave.getClave();

	tamaniostring=clavestring.length();

	tamaniotabla=tabla->GetTamanio();



	for(pos = 0; pos < tamaniostring; pos ++)
	{
		hash = hash+ (clavestring.at(pos)*(127^(tamaniotabla-pos)));

	}

	resultado=hash % tamaniotabla;


	return resultado;


}

int ExtensibleHashIndexStratregy::buscar(unsigned int posiciontabla)
{

	int nrocubo;

	nrocubo=tabla->DevolverNroCubo(posiciontabla);

	return nrocubo;
}

Bloque *ExtensibleHashIndexStratregy::leercubo(int nrocubo)
{
     Bloque *cubo;

 	cubo =this->Blockstorage->LeerBloque(nrocubo);


	return cubo;
}


int ExtensibleHashIndexStratregy::escribircubo(Bloque cubo, int nrocubo)
{

	list <Registro> *registros;

	registros=cubo.getRegistros();

	this->Blockstorage->GrabarBloque(cubo);

	return 1;
}

int ExtensibleHashIndexStratregy::verificarunicidad(Bloque cubo,Clave clave)
{
	int resultado;

	list <Registro> *registros;

    list <Registro>::iterator it;

    Registro registroaux;

    registros=cubo.getRegistros();

    for( it = registros->begin(); it != registros->end(); ++it )
    {

    	registroaux= (*it);

       	resultado=clave.Comparar(registroaux.getClave());

    	if (resultado==0)
    	{
    		return 0;

    	}

    }

	return 1;
}

void ExtensibleHashIndexStratregy::RepartirClaves(Bloque *cubo,Bloque *cuboaux,Registro reg, int tamanioreg)
{

	list <Registro> registrosaux;
	list <Registro> *ptrRegistros;
	Registro regaux;
	int Nrobloqueant;
	int Nrobloquenuevo;
	int posicion;
	int nrocubo;

	registrosaux=(*(cubo->getRegistros()));

	Nrobloqueant=cubo->getNroBloque();

	Nrobloquenuevo=cuboaux->getNroBloque();

	ptrRegistros= cubo->getRegistros();

	while (!ptrRegistros->empty())
	{
		ptrRegistros->pop_back();
	}


	cubo->setEspacioLibre(tamanioCubo);

	cuboaux->setEspacioLibre(tamanioCubo);


	while (!registrosaux.empty())
	{

		regaux=registrosaux.front();

		registrosaux.pop_front();

		posicion=funcionhash( *(regaux.getClave()) );

		nrocubo= buscar(posicion);


		if(nrocubo==Nrobloquenuevo)
		{
			AgregarRegABloque(regaux, cuboaux,tamanioreg);
		}

		if (nrocubo==Nrobloqueant)
		{
			AgregarRegABloque(regaux, cubo, tamanioreg);
		}


	};

	posicion=funcionhash( *(reg.getClave()) );

	nrocubo= buscar(posicion);

	if(nrocubo==Nrobloquenuevo)
		{

		//Si el registro nuevo se tiene que insertar en el bloque nuevo, se graba el bloque anterior, con
		//la modificacion en sus registros, y se intenta insertar el registro nuevo.

		escribircubo((*cubo),cubo->getNroBloque());

		Insercion(cuboaux,reg,posicion);

		}

	if (nrocubo==Nrobloqueant)
		{

			escribircubo((*cuboaux),cuboaux->getNroBloque());

			Insercion(cubo,reg,posicion);
		}

	return;

}

// Se ingresa al final de la lista de registros, el registro pasado por parametro
// Ademas se actualiza el espaciolibre
// Pre: Bloque tiene espacio suficiente para el registro nuevo
// Post: Bloque actualizado con registro nuevo y espacio libre

void ExtensibleHashIndexStratregy::AgregarRegABloque(Registro reg, Bloque *cubo, int tamanioreg)
{

	list <Registro> *registros;

	registros=cubo->getRegistros();

	registros->push_back(reg);

	// Insertar registros a Bloque

	cubo->setEspacioLibre(cubo->getEspaciolibre()-tamanioreg);


	return;
}


int ExtensibleHashIndexStratregy::QuitarRegABloque(Clave clave, Bloque *cubo, int tamanioreg)
{

	list <Registro> *registros;
	Registro regaux;
	int elimino=0;
    list <Registro>::iterator it;
    int resultado;

	registros=cubo->getRegistros();

	 for( it = registros->begin(); ((it != registros->end())&&(elimino==0 )); ++it )
	    {

	    	regaux= (*it);

	    	resultado=clave.Comparar(regaux.getClave());

	    	if (resultado==0)
	    	{
	    		it=registros->erase(it);

	    		elimino=1;

	    	}
	    }

	 if (elimino==0)
		{
			cout << "Error al eliminar registro" << endl;

			return -1;
		}

	cubo->setEspacioLibre(cubo->getEspaciolibre()+tamanioreg);

	if (registros->empty()) return 0; //El bloque se encuentra vacio luego de la eliminacion

	return 1;
}

int ExtensibleHashIndexStratregy::ActualizarTabla(int tamaniodispCubo, int posicion,int ultimonrobloque)
{

	int i = 0;
	int tamaniotabla;


	tamaniotabla= tabla->GetTamanio();

	if (tamaniodispCubo != 0)
	{
		for(i = posicion; i >= 0; i -= tamaniodispCubo)
			tabla->ModificarPosicion(ultimonrobloque,i);

		for(i = posicion; i < tamaniotabla; i += tamaniodispCubo)
			tabla->ModificarPosicion(ultimonrobloque,i);

    	return 0;


    }

	return 1;

}

int ExtensibleHashIndexStratregy::ObtenerProxNumeroBloque()
{
	int prox;

	if (NroCubosDisponibles.empty())
	{
		ultimonrobloque++;

		return ultimonrobloque;
	}
	//Se reutiliza Bloque disponible

	prox=NroCubosDisponibles.back();


	//Se elimina bloque ya utilizado
	NroCubosDisponibles.pop_back();

	return prox;
}

int ExtensibleHashIndexStratregy::Insercion(Bloque *cubo, Registro reg, int posicion)
{
	int tamaniodispCubo,espaciolibre;
	int nrocubo;
	int tamanioreg;

	tamanioreg=reg.getLongitudDato();

	tamaniodispCubo=TablaDisp->GetTamanioDisp(cubo->getNroBloque());

	if (tamaniodispCubo==-1)
	{
		cout << "El nro de bloque no existe, y no tiene tamanio de dispersion" << endl;
		return -1;
	}

	espaciolibre=(*cubo).getEspaciolibre();

	if(espaciolibre>=tamanioreg)
	{
		AgregarRegABloque(reg,cubo,tamanioreg);

		escribircubo((*cubo),nrocubo);


	return 1; //Se inserto elemento

	}

	if(espaciolibre<tamanioreg)
	{

		if (tamanioreg > tamanioCubo) return -1; // El tamanio del elemento a insertar es mayor al tamanio del bloque vacio


		ultimonrobloque=ObtenerProxNumeroBloque();

		TablaDisp->SetTamanioDisp(cubo->getNroBloque(),tamaniodispCubo*2);// Se duplica tamaño de dispersion

		if (tamaniodispCubo<tabla->GetTamanio())
		{
				ActualizarTabla(tamaniodispCubo*2, posicion,ultimonrobloque);

			}else
			{
				tabla->DuplicarTabla();
				tabla->ModificarPosicion(ultimonrobloque,posicion);
			}

		Bloque cuboaux (ultimonrobloque, tamanioCubo);

		cuboaux.setEspacioLibre(tamanioCubo);

		TablaDisp->SetTamanioDisp(ultimonrobloque,tamaniodispCubo*2); // Se crea una nueva posicion de la lista de dispersion

		RepartirClaves(cubo,&cuboaux,reg,tamanioreg);

		delete cuboaux.getRegistros();

	}

	return 1;

}

int ExtensibleHashIndexStratregy::Eliminar(Clave clave)
{
	int posicion, nrocubo,unico, tamanioreg;
	Bloque *cubo;
	int resultado, simetria, tamanioDisp;
	int nroCuboRemplazo;

	posicion=funcionhash(clave);

	nrocubo= buscar(posicion);

	if (nrocubo == -1) return -1; // ERROR EN INDICE HASH; SE DIRECCIONA A UNA POSICION INEXISTENTE

	cubo = leercubo(nrocubo);

	unico=verificarunicidad(*cubo,clave);

	if (unico!=0)
		{
		delete cubo->getRegistros();
		delete cubo;

		return -1; // No existe la clave
		}

	resultado=QuitarRegABloque(clave, cubo,tamanioreg);

	if (resultado==-1)
		{
			delete cubo->getRegistros();
			delete cubo;
			return 0; // Error, se quiere quitar registro inexistente
		}

	escribircubo(*cubo,cubo->getNroBloque());

	delete cubo->getRegistros();
	delete cubo;

	if (resultado==0) //El bloque queda vacio
	{

		tamanioDisp=TablaDisp->GetTamanioDisp(nrocubo);
		if (tamanioDisp==-1)
		{
			cout << "El nro de bloque no existe, y no tiene tamanio de dispersion" << endl;
			return -1;
		}

		nroCuboRemplazo=tabla->AnalizarBaja(posicion, tamanioDisp);

		if(nroCuboRemplazo==-1) return 1; //Se elimino registro, pero no puede eliminarse bloque  de tabla de direcciones

		//Se sobreescribe la posicion de la tabla con el cubo de reemplazo
		tabla->ModificarPosicion(nroCuboRemplazo,posicion);

		// Se divide su tamaño de dispersion
		TablaDisp->SetTamanioDisp(nroCuboRemplazo,(TablaDisp->GetTamanioDisp(nroCuboRemplazo)/2));

		//Se escribe nro de bloque para que sea reutilizado
		this->NroCubosDisponibles.push_back(nrocubo);


		simetria=tabla->VerificarSimetria();

		if (simetria == 0)
		{
			resultado=tabla->AcortarTabla();
		}

	}

	return 1;

}

int ExtensibleHashIndexStratregy::Modificar(Clave clave, Dato *dato)
{

	    int posicion, nrocubo,unico, tamanioreg,resultado;
		Bloque *cubo;
		string datoSerializado;
		int resultadoInsercion;

		posicion=funcionhash(clave);

		nrocubo= buscar(posicion);

		if (nrocubo == -1) return -1; // ERROR EN INDICE HASH; SE DIRECCIONA A UNA POSICION INEXISTENTE

		cubo = leercubo(nrocubo);

		unico=verificarunicidad(*cubo,clave);

		if (unico!=0)
			{
			delete cubo->getRegistros();
			delete cubo;
			return -1; // No existe la clave
			}

		resultado=QuitarRegABloque(clave, cubo,tamanioreg);

		datoSerializado=dato->serializar();

		Registro reg(datoSerializado,clave);

		if (resultado==-1)
			{
				delete cubo->getRegistros();
				delete cubo;
				return 0; // Error, se quiere quitar registro inexistente
			}


		resultadoInsercion= Insercion(cubo,reg,posicion);

		delete cubo->getRegistros();
		delete cubo;
		if (resultadoInsercion==-1) return -1; // No se puede modificar Elemento. Dato demasiado grande

	return 1;

}

void ExtensibleHashIndexStratregy::saludar()
{
		cout << "Soy una estrategia indice hash "<< endl;
}

bool ExtensibleHashIndexStratregy::checkUniqueness(Clave *clave, Dato *dato){

	int nrocubo;
	int unico;
	int posicion; //Posicion de tabla devuelta por funcion Hash
	Bloque *cubo;
	string datoSerializado;

	posicion=funcionhash(*clave);

	nrocubo= buscar(posicion);

	if (nrocubo == -1) return -1; // ERROR EN INDICE HASH; SE DIRECCIONA A UNA POSICION INEXISTENTE

	cubo = leercubo(nrocubo);

	unico=verificarunicidad(*cubo,*clave);

	delete cubo->getRegistros();
	delete cubo;

	cubo=NULL;

	if (unico==0)
		{
		cout << "CLave duplicada " << endl;
		return false; //Clave duplicada
		}


	return true;



}

void ExtensibleHashIndexStratregy::updateIndex(int fileReference,Clave *clave,Dato *dato){


	int nrocubo,unico;
	int posicion; //Posicion de tabla devuelta por funcion Hash
	Bloque *cubo;
	string datoSerializado;


	posicion=funcionhash(*clave);


	nrocubo= buscar(posicion);
	if (nrocubo == -1) return ; // ERROR EN INDICE HASH; SE DIRECCIONA A UNA POSICION INEXISTENTE


	cubo = leercubo(nrocubo);


	// SE OBTIENE DATO Y SE INSTANCIA REGISTRO
	datoSerializado=dato->serializar();


	Registro reg(datoSerializado,*clave);

	unico=verificarunicidad(*cubo,*clave);


	if (unico==0) // Clave Ya existente, se actualiza
		{

		delete cubo->getRegistros();
		delete cubo;

		this->Modificar(*clave,dato);

		return;
		}

	Insercion(cubo,reg,posicion);

	delete cubo->getRegistros();
	delete cubo;

}

int ExtensibleHashIndexStratregy::getStorageReferenceByKey(Clave *clave)
{
	int posicion,nrocubo;

	posicion=funcionhash(*clave);

	nrocubo= buscar(posicion);

	return nrocubo;


}

int ExtensibleHashIndexStratregy::deleteKey(Clave *clave) {
	int resultado;

	resultado=this->Eliminar(*clave);

	return resultado;
}

/**
 * Workaround agregado, dado que las distintas estrategias de indexado comparten una misma interfaz, y su comportamiento no se el mismo
 * Problema de Arquitectura.
 */
int ExtensibleHashIndexStratregy::getClassName(){
	return Hash;
}

Almacenamiento *ExtensibleHashIndexStratregy::getStorage() const{
	cout << "ExtensibleHashIndexStratregy::getStorage | No implementada " << endl;
	return NULL;
}

void ExtensibleHashIndexStratregy::setStorage(Almacenamiento* storage){
	cout << "IndexStrategy::setStorage | No implementada " << endl;
}

